package com.an.blog.service.impl;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.an.blog.admin.domain.BlogArticle;
import com.an.blog.admin.domain.BlogComment;
import com.an.blog.mapper.BlogArticleMapper;
import com.an.blog.mapper.BlogCommentMapper;
import com.an.blog.service.IBlogUserService;
import com.an.blog.strategy.context.SocialLoginStrategyContext;
import com.an.blog.web.domain.BlogBindBo;
import com.an.blog.web.domain.BlogLoginForm;
import com.an.blog.web.domain.bo.BlogForgetUserBo;
import com.an.blog.web.domain.bo.BlogRegisterUserBo;
import com.an.blog.web.domain.vo.BlogAuthorInfoVo;
import com.an.blog.web.domain.vo.BlogUserWebVo;
import com.an.common.constant.BlogConstants;
import com.an.common.constant.CacheConstants;
import com.an.common.constant.Constants;
import com.an.common.constant.SysConfigConstants;
import com.an.common.core.domain.R;
import com.an.common.core.domain.entity.SysUser;
import com.an.common.core.domain.event.LogininforEvent;
import com.an.common.core.domain.model.LoginUser;
import com.an.common.enums.DeviceType;
import com.an.common.enums.UserStatus;
import com.an.common.enums.UserType;
import com.an.common.exception.blog.BlogException;
import com.an.common.exception.user.UserException;
import com.an.common.helper.LoginHelper;
import com.an.common.utils.MessageUtils;
import com.an.common.utils.ServletUtils;
import com.an.common.utils.StringUtils;
import com.an.common.utils.redis.RedisUtils;
import com.an.common.utils.spring.SpringUtils;
import com.an.system.mapper.SysUserMapper;
import com.an.system.service.ISysConfigService;
import com.an.system.service.SysLoginService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 博客用户Service业务层处理
 *
 * @author Ann
 * @date 2023-05-30
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class BlogUserServiceImpl implements IBlogUserService {

    private final SysUserMapper userMapper;
    private final SysLoginService loginService;
    private final ISysConfigService sysConfigService;
    private final BlogArticleMapper articleMapper;
    private final BlogCommentMapper commentMapper;

    private final SocialLoginStrategyContext socialLoginStrategyContext;


    /**
     * 注册
     *
     * @param registerUserBo 注册用户业务对象
     * @return {@link Map }<{@link String },{@link Object }>
     * @author: Ann
     * @createDate: 2023/09/01 13:45:34
     * @version: 1.0.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> register(BlogRegisterUserBo registerUserBo) {
        //判断两次密码是否一致，返回blogUser,防止频繁创建对象
        SysUser sysUser = validBeforeRegister(registerUserBo);
        //判断是否是社交登录时的注册
        boolean isSocialRegister = StringUtils.isNotEmpty(registerUserBo.getSource()) &&
            StringUtils.isNotEmpty(registerUserBo.getState()) &&
            StringUtils.isNotEmpty(registerUserBo.getSocialCode());
        //构建插入对象
        sysUser.setUserName(registerUserBo.getEmail());
        sysUser.setUserType(UserType.BLOG_USER.getUserType());
        sysUser.setEmail(registerUserBo.getEmail());
        sysUser.setSex(BlogConstants.MALE);
        sysUser.setPassword(BCrypt.hashpw(registerUserBo.getPassword()));
        sysUser.setNickName(BlogConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomNumbers(7));
        if (!isSocialRegister) {
            //查询默认头像
            String defaultAvatar = sysConfigService.selectConfigByKey(SysConfigConstants.BLOG_USER_DEFAULT_AVATAR);
            sysUser.setAvatar(defaultAvatar);
        }
        boolean flag = userMapper.insert(sysUser) > 0;
        //构建好要返回的对象
        Long userId = sysUser.getUserId();
        BlogUserWebVo blogUserWebVo = new BlogUserWebVo().setUserId(userId).setNickName(sysUser.getNickName())
            .setAvatar(sysUser.getAvatar()).setSex(sysUser.getSex());

        if (isSocialRegister && flag) {
            //如果是社交登录，注册成功，插入中间表进行绑定
            BlogBindBo blogBindBo = new BlogBindBo();
            blogBindBo.setSource(registerUserBo.getSource())
                .setState(registerUserBo.getState())
                .setSocialCode(registerUserBo.getSocialCode());
            AuthUser authUser = socialLoginStrategyContext.executeRegisterBindSocialUser(blogBindBo, sysUser);
            //这里使用第三方的头像
            String avatar = authUser.getAvatar();
            String nickname = authUser.getNickname();
            blogUserWebVo.setNickName(nickname);
            blogUserWebVo.setAvatar(avatar);
            //更新数据库头像信息
            sysUser.setAvatar(avatar);
            sysUser.setNickName(nickname);
            userMapper.updateById(sysUser);
        }
        LoginHelper.login4Blog(sysUser, DeviceType.BLOG);
        // 记录日志
        recordBlogLogininfor(sysUser.getEmail(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.registerAndLogin.success"));
        loginService.recordLoginInfo(userId, sysUser.getUserName());
        String token = StpUtil.getTokenValue();
        Map<String, Object> ajax = new HashMap<>(1);
        ajax.put("token", token);
        return ajax;
    }


    /**
     * 登录
     *
     * @param blogLoginForm 博客登录表单
     * @param loginType     登录类型
     * @return {@link Map }<{@link String }, {@link Object }>
     * @author: Ann
     * @createDate: 2023/08/22 10:19:43
     * @version: 1.0.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> login(BlogLoginForm blogLoginForm, String loginType) {
        SysUser sysUser = loadBlogUserByEmail(blogLoginForm.getEmail());
        if (loginType.equals(BlogConstants.PWD_LOGIN)) {
            if (!BCrypt.checkpw(blogLoginForm.getPassword(), sysUser.getPassword())) {
                throw new BlogException("blog.web.user.pwd.not.correct");
            }
        } else {
            checkRedisCode(CacheConstants.MAIL_LOGIN_CODE_KEY, blogLoginForm.getEmail(), blogLoginForm.getCode());
        }
        //判定是社交绑定，绑定社交账号扩展其他策略，如QQ
        boolean isSocialLogin = StringUtils.isNotEmpty(blogLoginForm.getSource()) &&
            StringUtils.isNotEmpty(blogLoginForm.getState()) &&
            StringUtils.isNotEmpty(blogLoginForm.getSocialCode());
        if (isSocialLogin) {
            BlogBindBo blogBindBo = new BlogBindBo()
                .setSource(blogLoginForm.getSource()).setState(blogLoginForm.getState())
                .setSocialCode(blogLoginForm.getSocialCode());
            socialLoginStrategyContext.executeLoginBindSocialUser(blogBindBo, sysUser);
        }
        LoginHelper.login4Blog(sysUser, DeviceType.BLOG);
        // 记录日志
        recordBlogLogininfor(sysUser.getEmail(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
        loginService.recordLoginInfo(sysUser.getUserId(), sysUser.getUserName());
        String token = StpUtil.getTokenValue();
        Map<String, Object> ajax = new HashMap<>(2);
        ajax.put("token", token);
        return ajax;
    }


    /**
     * 注销
     *
     * @author: Ann
     * @createDate: 2023/06/06 15:24:28
     * @version: 1.0.0
     */
    @Override
    public void logout() {
        try {
            LoginUser loginUser = LoginHelper.getLoginUser();
            StpUtil.logout();
            recordBlogLogininfor(loginUser.getEmail(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
        } catch (NotLoginException ignored) {
        }
    }


    /**
     * 忘记 PWD
     *
     * @param forgetUserBo 忘记用户 bo
     * @return {@link Boolean }
     * @author Ann
     * @date 2024/08/09
     */
    @Override
    public Boolean forgetPwd(BlogForgetUserBo forgetUserBo) {
        if (!StringUtils.equals(forgetUserBo.getPassword(), forgetUserBo.getEnsurePassword())) {
            throw new BlogException("blog.web.user.pwd.not.equal");
        }
        checkRedisCode(CacheConstants.MAIL_FORGOT_CODE_KEY, forgetUserBo.getEmail(), forgetUserBo.getCode());
        SysUser sysUser = loadBlogUserByEmail(forgetUserBo.getEmail());
        sysUser.setPassword(BCrypt.hashpw(forgetUserBo.getPassword()));
        return userMapper.updateById(sysUser) > 0;

    }

    /**
     * oauth登录
     *
     * @param response        响应
     * @param source          源
     * @param callback        回调
     * @param servletResponse servlet响应
     * @author: Ann
     * @createDate: 2023/08/28 21:04:23
     * @version: 1.0.0
     */
    @Override
    public void oauthLogin(AuthResponse<AuthUser> response, String source, AuthCallback callback,
                           HttpServletResponse servletResponse) throws IOException {
        //策略模式执行对应社会化登录
        socialLoginStrategyContext.executeCallBackStrategy(source, response, callback, servletResponse);
    }

    /**
     * 让博客用户信息
     *
     * @return {@link R }<{@link BlogUserWebVo }>
     * @author: Ann
     * @createDate: 2023/08/28 20:05:19
     * @version: 1.0.0
     */
    @Override
    public R<BlogUserWebVo> getBlogUserInfo() {
        LoginUser loginUser = LoginHelper.getLoginUser();
        Long userId = loginUser.getUserId();
        SysUser sysUser = userMapper.selectUserById(userId);
        BlogUserWebVo blogUserWebVo = new BlogUserWebVo().setUserId(userId)
            .setAvatar(sysUser.getAvatar())
            .setSex(sysUser.getSex())
            .setNickName(sysUser.getNickName());
        return R.ok(blogUserWebVo);
    }

    /**
     * 查询博客作者信息
     * <p>
     * <p>
     * TODO 后期优化
     *
     * @return {@link R }<{@link BlogAuthorInfoVo }>
     * @author Ann
     * @date 2024/08/09
     */
    @Override
    public BlogAuthorInfoVo queryBlogAuthorInfo() {
        //查询用户信息
        SysUser sysUser = userMapper.selectVoById(1);
        //查询文章数量
        Long articleCount = articleMapper.selectCount(new LambdaQueryWrapper<BlogArticle>()
            .eq(BlogArticle::getStatus, BlogConstants.NORMAL));
        Long articleLikedCount = articleMapper.selectLikedSum();
        //查询文章评论数
        Long articleCommentedCount = commentMapper.selectCount(new LambdaQueryWrapper<BlogComment>()
            .eq(BlogComment::getTopic, BlogConstants.COMMENT_ARTICLE_TOPIC));
        //查询es是否开启
        BlogAuthorInfoVo blogAuthorInfoVo = new BlogAuthorInfoVo();
        blogAuthorInfoVo.setNickName(sysUser.getNickName());
        blogAuthorInfoVo.setAvatar(sysUser.getAvatar());
        blogAuthorInfoVo.setArticleCount(articleCount);
        blogAuthorInfoVo.setArticleLikedCount(articleLikedCount);
        blogAuthorInfoVo.setArticleCommentedCount(articleCommentedCount);
        return blogAuthorInfoVo;
    }

    /**
     * 记录博客logininfor
     *
     * @param email   电子邮件
     * @param status  状态
     * @param message 消息
     * @author: Ann
     * @createDate: 2023/06/06 22:35:56
     * @version: 1.0.0
     */
    private void recordBlogLogininfor(String email, String status, String message) {
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setUsername(email);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        logininforEvent.setWebsiteType(Constants.PORTAL_WEBSITE);
        logininforEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(logininforEvent);

    }


    /**
     * 负载博客用户通过电子邮件
     *
     * @param email 电子邮件
     * @return {@link SysUser }
     * @author: Ann
     * @createDate: 2023/06/06 17:21:00
     * @version: 1.0.0
     */
    private SysUser loadBlogUserByEmail(String email) {
        SysUser blogUser = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
        if (ObjectUtil.isNull(blogUser)) {
            log.info("登录用户：{} 不存在.", email);
            throw new UserException("blog.web.user.not.exist", email);
        } else if (UserStatus.DISABLE.getCode().equals(blogUser.getStatus())) {
            log.info("登录用户：{} 已被停用.", email);
            throw new UserException("blog.web.user.blocked", email);
        }
        return blogUser;
    }

    /**
     * 有效注册之前
     *
     * @param registerUserBo 注册用户业务对象
     * @return {@link SysUser }
     * @author: Ann
     * @createDate: 2023/06/05 10:13:25
     * @version: 1.0.0
     */
    private SysUser validBeforeRegister(BlogRegisterUserBo registerUserBo) {
        //检查两次密码是否相同
        if (!StringUtils.equals(registerUserBo.getPassword(), registerUserBo.getEnsurePassword())) {
            throw new BlogException("blog.web.user.pwd.not.equal");
        }
        SysUser sysUser = new SysUser();
        sysUser.setUserName(registerUserBo.getEmail());
        // 检查用户名重复
        if (!checkUserNameUnique(sysUser)) {
            throw new BlogException("blog.web.user.save.username.repeat.error");
        }
        if (!checkEmailUnique(sysUser)) {
            throw new BlogException("blog.web.user.save.email.repeat.error");
        }
        checkRedisCode(CacheConstants.MAIL_REGISTER_CODE_KEY, registerUserBo.getEmail(), registerUserBo.getCode());
        return sysUser;
    }

    /**
     * 检查复述,代码
     *
     * @param keyPrefix  关键前缀
     * @param email      电子邮件
     * @param commitCode 提交代码
     * @author: Ann
     * @createDate: 2023/08/22 10:24:15
     * @version: 1.0.0
     */
    private static void checkRedisCode(String keyPrefix, String email, String commitCode) {
        String code = RedisUtils.getCacheObject(keyPrefix + email);
        //检查验证码是否正确并且未过期
        if (ObjectUtil.isNull(code)) {
            throw new BlogException("blog.web.mail.code.expired");
        }
        if (!commitCode.equals(code)) {
            throw new BlogException("blog.web.mail.code.error");
        }
    }


    /**
     * 检查邮箱是否唯一
     *
     * @param sysUser 系统用户
     * @return boolean
     * @author: Ann
     * @createDate: 2023/09/24 11:05:41
     * @version: 1.0.0
     */
    private boolean checkEmailUnique(SysUser sysUser) {
        boolean exist = userMapper.exists(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, sysUser.getUserName())
            .ne(ObjectUtil.isNotNull(sysUser.getUserId()), SysUser::getUserId, sysUser.getUserId()));
        return !exist;
    }

    /**
     * 检查用户名是否唯一
     *
     * @param sysUser 系统用户
     * @return boolean
     * @author: Ann
     * @createDate: 2023/09/24 11:05:48
     * @version: 1.0.0
     */
    private boolean checkUserNameUnique(SysUser sysUser) {
        boolean exist = userMapper.exists(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, sysUser.getUserName())
            .ne(ObjectUtil.isNotNull(sysUser.getUserId()), SysUser::getUserId, sysUser.getUserId()));
        return !exist;
    }

}
